;;; nodejs.el --- NodeJS repl with nvm
;; Copyright (C) 2015 Stefano Mazzucco

;; Author: Stefano Mazzucco <stefano@curso.re>
;; Keywords: nodejs, repl, comint, nvm, javascript
;; URL: https://notabug.org/stefano-m/.emacs.d
;; Version: 20151116
;; X-Original-Version: 0.1

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, version 3 of the License.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

;; This file IS NOT part of GNU Emacs.

;;; Commentary:
;;; NOTE: most of this functionality has been merged into js-comint v0.0.3
;;; Extend nvm.el with an interactive function to chose the node version and a comint mode.
;;;
;;; * do-nvm-use: select nvm version interactively
;;; * run-node: run the version of node selected with do-nvm-use

;;; References and Credits:
;;; http://www.williamjohnbert.com/2014/02/using-a-node-repl-with-emacs/
;;; http://github.com/porterjamesj/virtualenvwrapper.el
;;; https://masteringemacs.org/article/comint-writing-command-interpreter

;;; Code:
(require 'nvm)
(require 'comint)

;;;;;;;;;;;; nvm functionality
(defvar nvm-history nil "The history of node versions used.")
(defvar nvm-current-version nil "Current version of node.")

(defun nvm-clear-history ()
  "Clear nvm history."
  (setq nvm-history nil))

(defun nvm--purge-history (candidates)
  "Remove history candidates that are not present in the list CANDIDATES."
  (setq nvm-history (-filter (lambda (s) (not (-contains? candidates s)))
                             nvm-history)))

(defun nvm-list-versions (prompt)
  "List available nvm version prompting the user with the string PROMPT."
  (let ((candidates (mapcar 'car (nvm--installed-versions))))
    (nvm--purge-history candidates)
    (completing-read prompt
                     candidates nil t nil
                     'nvm-history
                     (or (car nvm-history)
                         (car candidates)))))

;;;###autoload
(defun do-nvm-use (&optional version)
  "Use a given VERSION of node from nvm."
  (warn "NOTE: most of this functionality has been merged into js-comint v0.0.3")
  (interactive)
  (if version
      (setq nvm-current-version (nvm--find-exact-version-for version)))
  (let ((old-nvm nvm-current-version))
    (setq nvm-current-version
          (nvm-list-versions
           (if old-nvm
               (format "Nvm version (current %s): " (car old-nvm))
             "Nvm version: "))))
  (nvm-use nvm-current-version))

;;;;;;;;;;;; nodejs functionality
(defvar node-mode-map
  (let ((map (nconc (make-sparse-keymap) comint-mode-map)))
    map)
  "Basic mode map for `run-node'.")

(defvar node-prompt-regexp "^\\(?:> \\)"
  "Prompt for `run-node'.")

(defvar node-cli-arguments '("--interactive")
  "Commandline arguments to pass to NodeJS.")

(defun node--initialize ()
  "Helper function to initialize NodeJS."
  (setq comint-process-echoes t)
  (setq comint-use-prompt-regexp t))

(defun node--comint-buffer-name ()
  "Comint buffer name for NodeJS."
  (format "NodeJS %s" (car nvm-current-version)))

(define-derived-mode node-mode comint-mode "NodeJS"
  "Major mode for `run-node'.

\\<node-mode-map>"
  nil "NodeJS"
  ;; this sets up the prompt so it matches things like: [foo@bar]
  (setq comint-prompt-regexp node-prompt-regexp)
  ;; this makes it so commands like M-{ and M-} work.
  (set (make-local-variable 'paragraph-separate) "\\'")
  (set (make-local-variable 'paragraph-start) node-prompt-regexp))

;; this has to be done in a hook. grumble grumble.
(add-hook 'node-mode-hook 'node--initialize)

;;;###autoload
(defun run-node ()
  "Run nodejs."
  (warn "NOTE: most of this functionality has been merged into js-comint v0.0.3")
  (interactive)
  (unless nvm-current-version
    (do-nvm-use))
  (let* ((node-program (concat (nth 1 nvm-current-version) "/bin" "/node"))
         (buffer (comint-check-proc (node--comint-buffer-name))))
    (setenv "NODE_NO_READLINE" "1")
    ;; pop to the "*NodeJS <VERSION>*" buffer if the process is dead, the
    ;; buffer is missing or it's got the wrong mode.
    (pop-to-buffer-same-window
     (if (or buffer (not (derived-mode-p 'node-mode))
             (comint-check-proc (current-buffer)))
         (get-buffer-create (or buffer (format "*%s*" (node--comint-buffer-name))))
       (current-buffer)))
    ;; create the comint process if there is no buffer.
    (unless buffer
      (apply 'make-comint-in-buffer (node--comint-buffer-name) buffer
             node-program nil node-cli-arguments)
      (node-mode))))


(provide 'nodejs)
;;; nodejs.el ends here
